
`ifndef __PSM_SOC__V__ 
`define __PSM_SOC__V__
/*--------------------------------------------*\
psm 状态机文件，使用opbasm 进行编程
obpasm参考文档：http://kevinpt.github.io/opbasm/rst/m4.html

每条指令需要2个系统时钟

例化模板
psm_soc #(
    .memfile("firmware.hex") ,  // 固件
    .memnum ( 1024)  , // 固件大小 <= 1024
	.stack_size  (4) ,  // 1 ~ 32 
	.scratch_size( 256) // 1 ~ 256  
) psm_soc_u1 (
    .clk (clk) ,
    .reset(~rst_n ) ,
    .in_port (in_port ),
    .out_port(out_port) ,
    .port_id (port_id ),
    .write_strobe(write_strobe) ,
    .interrupt(interrupt) ,
    .interrupt_ack()
); 
\*---------------------------------------------*/

// 定义每条指令执行时的节拍数 ，不定义时为标准的 2的clk ， 定义时需要 >= 3
// 在quartus定义 PSM_JP_NUM 为 2 可以节省一点逻辑资源
`define PSM_JP_NUM 2

// 定义使能清空初始值
`define PSM_ENABLE_INITIAL  

//psm_inc
`define reset_vector      10'd0
`define interrupt_vector  10'h3FF

`define flag_z            2'b00
`define flag_nz           2'b01
`define flag_c            2'b10 
`define flag_nc           2'b11

`define opcode_rr         2'b10
`define opcode_rl         2'b01
`define opcode_rsa        2'b00

`define opcode_load       5'h00
`define opcode_add        5'h0C
`define opcode_addcy      5'h0D
`define opcode_and        5'h05
`define opcode_compare    5'h0A
`define opcode_or         5'h06
`define opcode_rs         5'h10
`define opcode_sub        5'h0E
`define opcode_subcy      5'h0F
`define opcode_test       5'h09
`define opcode_xor        5'h07
`define opcode_jump       5'h1A
`define opcode_call       5'h18
`define opcode_return     5'h15
`define opcode_returni    5'h1C
`define opcode_interrupt  5'h1E
`define opcode_input      5'h02
`define opcode_output     5'h16
`define opcode_fetch      5'h03
`define opcode_store      5'h17

`define op_load           `opcode_load     
`define op_add            `opcode_add      
`define op_addcy          `opcode_addcy    
`define op_and            `opcode_and      
`define op_compare        `opcode_compare  
`define op_or             `opcode_or       
`define op_rs             `opcode_rs       
`define op_sub            `opcode_sub      
`define op_subcy          `opcode_subcy    
`define op_test           `opcode_test     
`define op_xor            `opcode_xor      
`define op_jump           `opcode_jump     
`define op_call           `opcode_call     
`define op_return         `opcode_return   
`define op_returni        `opcode_returni  
`define op_interrupt      `opcode_interrupt
`define op_input          `opcode_input    
`define op_output         `opcode_output   
`define op_fetch          `opcode_fetch    
`define op_store          `opcode_store    



// psm_ram  / dpram
module psm_dpram (
	address_a,
	address_b,
	clock,
	data_a,
	data_b,
	wren_a,
	wren_b,
	q_a,
	q_b);

	input	[3:0]  address_a;
	input	[3:0]  address_b;
	input	  clock;
	input	[7:0]  data_a;
	input	[7:0]  data_b;
	input	  wren_a;
	input	  wren_b;
	output	 [7:0]  q_a;
	output	 [7:0]  q_b;

	reg [7:0 ] _mem [0:15]; 
	always @(posedge clock) begin
		if(wren_a) 
			_mem[address_a] <= data_a ;
		else if (wren_b) 
			_mem[address_b] <= data_b ; 
	end	


`ifdef PSM_JP_NUM
	reg [7:0] q_a_r , q_b_r ;
	assign q_a = q_a_r ;
	assign q_b = q_b_r ;
	always @(posedge clock ) begin
		if(~wren_a)q_a_r <= _mem[address_a] ;
		if(~wren_b)q_b_r <= _mem[address_b] ;
	end
`else
	assign q_a = _mem[address_a] ;
	assign q_b = _mem[address_b] ;
`endif 
	
`ifdef PSM_ENABLE_INITIAL 
  integer i ; 
  initial begin 
    for(i=0;i<16;i=i+1)
      _mem[i] = 0 ;
  end
`endif 

endmodule


// psm_alu
 
module psm_alu (
  input [4:0]     operation,
  input [2:0]     shift_operation,
  input           shift_direction,
  input           shift_constant,
  input [7:0]     operand_a,
  input [7:0]     operand_b,
  input           carry_in,
  output reg      carry_out,
  output          zero_out,
  output reg[7:0] result
);
 
/*
****************************************************************************************************
*                                     Adder/Subtracter
****************************************************************************************************
*/
  wire[7:0]   addsub_b;
  reg         addsub_carry;
  wire[8:0]   addsub_result;
  assign addsub_b = (
                      (operation==`op_sub) || 
                      (operation==`op_subcy) ||
                      (operation==`op_compare)
                    ) ? ~operand_b : operand_b;
  
  always@(operation, carry_in) begin
    if(operation==`op_addcy)     
      addsub_carry = carry_in;  //result = a + b + carry_in;
    else if((operation==`op_sub)||(operation==`op_compare))
      addsub_carry = 1'b1;      //result = a - b = a + (~b + 1)
    else if(operation==`op_subcy)
      addsub_carry = ~carry_in; //result = a - b - carry_in = a + ~b + ~carry_in;
    else
      addsub_carry = 1'b0; 
  end
  
  assign addsub_result = operand_a + addsub_b + addsub_carry;
  
/*
****************************************************************************************************
*                                     Shift & Rotate
****************************************************************************************************
*/
  reg         shift_bit;  
  always@(shift_operation, operand_a[0], operand_a[7], carry_in, shift_constant) begin
    if(shift_operation == `opcode_rr)
      shift_bit = operand_a[0];   //"SLX", "RR"
    else if(shift_operation == `opcode_rl)
      shift_bit = operand_a[7];   //"SRX", "RL"
    else if(shift_operation == `opcode_rsa)
      shift_bit = carry_in;       //"SLA", "SRA"
    else
      shift_bit = shift_constant; //"SL0", "SL1",  "SR0", "SR1",
  end
  
/*
****************************************************************************************************
*                                       ALU Result
****************************************************************************************************
*/  
  assign zero_out = ~(|result);
  always@(operation, shift_operation, shift_direction, shift_constant,
          shift_bit, result, operand_a, operand_b, carry_in, carry_out,
          addsub_result, addsub_carry) begin
    carry_out = 0;  
    case (operation)
      `op_add, `op_addcy: 
        {carry_out, result} = addsub_result;
      `op_compare, `op_sub, `op_subcy:
        {carry_out, result} = {~addsub_result[8], addsub_result[7:0]};
      `op_and:
        result = operand_a & operand_b;
      `op_or:
        result = operand_a | operand_b;
      `op_xor:
        result = operand_a ^ operand_b;
      `op_test: begin
        result = operand_a & operand_b;
        carry_out = ^result;
      end
      `op_rs: begin
        if(shift_direction)   //Shift Right
          {result, carry_out} = {shift_bit, operand_a};
        else                  //Shift Left
          {carry_out, result} = {operand_a, shift_bit};
      end
      default: result = operand_b;
    endcase     
  end
endmodule



// psm_idu 

module psm_idu (
  input [17:0]      instruction,
  output reg[4:0]   operation,
  output[2:0]       shift_operation,
  output            shift_direction,
  output            shift_constant,
  output[3:0]       x_address,
  output[3:0]       y_address,
  output[7:0]       implied_value,
  output[7:0]       port_address,
  output[7:0]       scratch_address,
  output[9:0]       code_address,
  output            conditional,
  output[1:0]       condition_flags,
  output            operand_selection,
  output            interrupt_enable
);

  wire[4:0] instruction_type = instruction[17:13];

  assign x_address          = instruction[11:8];
  assign y_address          = instruction[7:4];
  assign code_address       = instruction[9:0];
  assign scratch_address    = instruction[7:0];
  assign port_address       = instruction[7:0];
  assign implied_value      = instruction[7:0];
  assign operand_selection  = instruction[12];
  assign interrupt_enable   = instruction[0];
  
  assign shift_direction    = instruction[3];
  assign shift_operation    = instruction[2:1];
  assign shift_constant     = instruction[0];
  
  assign conditional        = instruction[12];
  assign condition_flags    = instruction[11:10];  
  
    always@(instruction_type) begin
      operation = instruction_type;
    end 
  /*
  always@(instruction_type) begin
    operation = 5'd0;
    case (instruction_type)
      `opcode_load      : operation = `op_load;
      `opcode_add       : operation = `op_add;
      `opcode_addcy     : operation = `op_addcy;      
      `opcode_and       : operation = `op_and;
      `opcode_or        : operation = `op_or;
      `opcode_rs        : operation = `op_rs;
      `opcode_sub       : operation = `op_sub;
      `opcode_subcy     : operation = `op_subcy;
      `opcode_xor       : operation = `op_xor;
      `opcode_jump      : operation = `op_jump;
      `opcode_call      : operation = `op_call;
      `opcode_return    : operation = `op_return;
      `opcode_returni   : operation = `op_returni;
      `opcode_interrupt : operation = `op_interrupt;
      `opcode_input     : operation = `op_input;
      `opcode_output    : operation = `op_output;
      `opcode_compare   : operation = `op_compare;
      `opcode_test      : operation = `op_test;
      `opcode_fetch     : operation = `op_fetch;
      `opcode_store     : operation = `op_store;
      default           : operation = 5'd0;
    endcase
  end
*/

endmodule

// psm_register 
module psm_register (
  input           clk,
  input           x_write_enable,
  input [3:0]     x_address,
  input [3:0]     y_address,
  input [7:0]     x_data_in,
//  output reg[7:0] x_data_out,
//  output reg[7:0] y_data_out
  output [7:0]    x_data_out,
  output [7:0]    y_data_out
);
/*
  reg[7:0]  dpr[15:0];
  always@(posedge clk) begin
    if(x_write_enable)
      dpr[x_address] <= x_data_in;
  end

  always@(posedge clk) begin
    x_data_out <= dpr[x_address];
    y_data_out <= dpr[y_address];
  end
  
  initial begin
    x_data_out = 8'd0;
    y_data_out = 8'd0;
    dpr[0]  = 8'd0;
    dpr[1]  = 8'd0;
    dpr[2]  = 8'd0;
    dpr[3]  = 8'd0;
    dpr[4]  = 8'd0;
    dpr[5]  = 8'd0;
    dpr[6]  = 8'd0;
    dpr[7]  = 8'd0;
    dpr[8]  = 8'd0;
    dpr[9]  = 8'd0;
    dpr[10] = 8'd0;
    dpr[11] = 8'd0;
    dpr[12] = 8'd0;
    dpr[13] = 8'd0;
    dpr[14] = 8'd0;
    dpr[15] = 8'd0;
  end
*/
  psm_dpram dpram (
    .address_a (x_address),
    .address_b (y_address),
    .clock     (clk),
    .data_a    (x_data_in),
    .data_b    (8'd0),
    .wren_a    (x_write_enable),
    .wren_b    (1'b0),
    .q_a       (x_data_out),
    .q_b       (y_data_out)
  );

endmodule




// psm_scratch 
module psm_scratch #(
  parameter  scratch_size = 256
) (
  input             clk,
  input             write_enable,
  input [7:0]       address,
  input [7:0]       data_in,
  output reg[7:0]   data_out
);

  // reg[7:0]  spr[255:0];
  (* ram_style = "block" *)
  reg[7:0]  spr[scratch_size-1 : 0 ]  /* synthesis syn_ramstyle = "block_ram" */ ;
  
  always@(posedge clk) begin
    if(write_enable)
      spr[address] <= data_in;  
  end
  
  always@(posedge clk) begin
    data_out <= spr[address];
  end
  
`ifdef PSM_ENABLE_INITIAL
  integer i;
  initial begin
    for(i=0; i<scratch_size; i=i+1) 
      spr[i] = 8'd0;
  end
`endif 

endmodule


// -----------------------

module psm_dpram_2 #(
    parameter DATA_DEPTH = 256 ,  // 数据深度 ， 存的数据个数
    parameter DATA_WIDTH = 8   ,  // 数据位宽
    parameter ADDR_WIDTH = 8   ,  // 地址位宽
    parameter ENABLE_ASYNC   = 0      // 是否设置输出异步
) (
	address_a,
	address_b,
	clock,
	data_a,
	data_b,
	wren_a,
	wren_b,
	q_a,
	q_b);

	input	[ADDR_WIDTH - 1:0]  address_a;
	input	[ADDR_WIDTH - 1:0]  address_b;
	input	  clock;
	input	[DATA_WIDTH - 1:0]  data_a;
	input	[DATA_WIDTH - 1:0]  data_b;
	input	  wren_a;
	input	  wren_b;
	output	 [DATA_WIDTH - 1:0]  q_a;
	output	 [DATA_WIDTH - 1:0]  q_b;

	(* ram_style="block" *) reg [DATA_WIDTH - 1:0 ] _mem [0:DATA_DEPTH-1] /* synthesis syn_ramstyle = "block_ram" */; 

	always @(posedge clock) begin
		if(wren_a) 
			_mem[address_a] <= data_a ;
		else if (wren_b) 
			_mem[address_b] <= data_b ; 
	end	

generate
    genvar  i , j ;
	 reg [DATA_WIDTH - 1:0] q_a_r , q_b_r ;
	 
    if(|ENABLE_ASYNC) begin

        always @(*) begin
            q_a_r = (~wren_a) ? _mem[address_a]:q_a_r ;
            q_b_r = (~wren_b) ? _mem[address_b]:q_b_r ;
        end

    end else begin 
        
        always @(posedge clock ) begin
            if(~wren_a)q_a_r <= _mem[address_a] ;
            if(~wren_b)q_b_r <= _mem[address_b] ;
        end

    end 
	 
	for(i=0;i<DATA_WIDTH;i=i+1) begin :out_blk
       assign q_a[i] = q_a_r[i] ;
       assign q_b[i] = q_a_r[i] ;
   end
endgenerate

	
endmodule


module psm_stack_ram #(
    parameter DATA_DEPTH = 8,
    parameter DATA_WIDTH = 8,
    parameter ADDR_WIDTH = 5 
)
(
    input clk, // 输入时钟信号
    input reset, // 输入复位信号
    input push, // 入栈信号
    input pop , 
    input [DATA_WIDTH - 1 : 0 ] i_data , 
    output [DATA_WIDTH - 1 :0] o_data // 输出栈顶信号
);


reg [ADDR_WIDTH -1 : 0 ] top ;
wire [ADDR_WIDTH -1 : 0 ] address ; 
assign address = top + push - pop;

reg [ DATA_WIDTH - 1 : 0 ] data_a ;
reg wren_a ;
always@(posedge clk) begin
    if(reset) begin 
        top <= {(ADDR_WIDTH){1'b1}}  ;
    end else 
    begin
        wren_a <= 1'b0;
        if(push) begin
            top <=  top + 1'b1;
            wren_a <= 1'b1;
            
        end 
   
        if( pop ) begin
            top <=  top - 1'b1  ;
        end
    end
end



always@(posedge clk) begin
  data_a <= i_data ;
end
  

psm_dpram_2 #(
    .DATA_DEPTH    ( DATA_DEPTH ) ,  // 数据深度 ， 存的数据个数
    .DATA_WIDTH    ( DATA_WIDTH    ),  // 数据位宽
    .ADDR_WIDTH    ( ADDR_WIDTH    ),  // 地址位宽
    .ENABLE_ASYNC  ( 0    )   // 是否设置输出异步 1：EDA会使用逻辑单元模拟  0: eda大概率会调用memory
) u_cbb_dpram (
	.address_a ( address) ,
	.address_b ( top) ,
	.clock     ( clk) ,
	.data_a    ( data_a) ,
	.data_b    ( 10'D0) ,
	.wren_a    ( wren_a) ,  // 高电平时写数据
	.wren_b    ( 1'b0) ,
	.q_a       ( ) ,
	.q_b       ( o_data) 
) ;

endmodule

// -----------------------
// psm_stack 
module psm_stack #(
	parameter stack_size = 32
)(
  input           clk,
  input           reset,
  input           write_enable,
  input           update_enable,
  input           push_pop,
  input [9:0]     data_in,
  output [9:0] data_out
);

wire update ; 
assign update = update_enable & write_enable;

psm_stack_ram # (
    .DATA_DEPTH ( stack_size),
    .DATA_WIDTH ( 10),
    .ADDR_WIDTH ( 5) 
) u_Stack (
    .clk    (clk), // 输入时钟信号
    .reset  (reset), // 输入复位信号
    .push   (update &(push_pop)), // 入栈信号
    .pop    (update &(~push_pop)), 
    .i_data (data_in), 
    .o_data (data_out) // 输出栈顶信号
);

endmodule


/*
module psm_stack #(
	parameter stack_size = 32
)(
  input           clk,
  input           reset,
  input           write_enable,
  input           update_enable,
  input           push_pop,
  input [9:0]     data_in,
  output [9:0] data_out
);

  reg [9:0]   spr[stack_size-1:0];
  reg [4:0]   ptr;
  
  wire[4:0]  ptr_nxt = push_pop ? ptr+5'd1 : ptr-5'd1;
  
`ifdef PSM_JP_NUM
  reg [9:0] data_out_r ;
  assign data_out = data_out_r ;
  always@(posedge clk) begin
    data_out_r <= spr[ptr_nxt];
  end
`else
  assign data_out = spr[ptr_nxt];
`endif 

  always@(posedge clk) begin
    if(reset) ptr <= 5'd0;
    else begin
      if(write_enable)  spr[ptr] <= data_in;
      if(update_enable) ptr <= ptr_nxt;
    end
  end
  
  integer i;
  initial begin
    for(i=0; i<stack_size; i=i+1)
      spr[i] <= 10'd0;
  end

endmodule
*/





// psm  top 

module psm #(
  parameter scratch_size = 256 ,
  parameter stack_size  = 32 
)(
  input         clk,
  input         reset,
  input [17:0]  instruction,
  input         interrupt,
  input [7:0]   in_port,  
  output[9:0]   address,
  output reg    write_strobe,
  output reg    read_strobe,
  output[7:0]   port_id,
  output[7:0]   out_port,
  output reg    interrupt_ack
);
/*
****************************************************************************************************
*                                             Signals
****************************************************************************************************
*/
  reg [9:0]   program_counter=10'd0;
  reg [9:0]   program_counter_source=10'd0;
  wire[9:0]   program_counter_next;
 
`ifdef PSM_JP_NUM
  reg [`PSM_JP_NUM-1:0]   timing_control= {{(`PSM_JP_NUM-1){1'b0}} , 1'b1};
`else 
  reg [1:0]   timing_control=2'b01;
`endif
  
  wire        internal_reset;
  reg[2:0]    reset_latch=3'b000;
  
  wire        interrupt_assert;
  reg         interrupt_enable=1'b0;
  reg         interrupt_latch=1'b0;
  
  reg         zero=1'b0;
  reg         carry=1'b0;
  reg         zero_saved=1'b0;
  reg         carry_saved=1'b0;
  reg         zero_carry_write_enable=1'b0;
  
  wire        conditional_match;
  wire        is_jump;   
  wire        is_call;   
  wire        is_return; 
  wire        is_returni;
  wire        is_input;  
  wire        is_fetch;
  
  //IDU ports
  wire[4:0]   idu_operation;
  wire[2:0]   idu_shift_operation;
  wire        idu_shift_direction;
  wire        idu_shift_constant;
  wire        idu_operand_selection;
  wire[3:0]   idu_x_address, idu_y_address;
  wire[7:0]   idu_implied_value;
  wire[7:0]   idu_port_address;
  wire[5:0]   idu_scratch_address;
  wire[9:0]   idu_code_address;
  wire        idu_conditional;
  wire[1:0]   idu_condition_flags;
  wire        idu_interrupt_enable;  
  
  //ALU Ports
  wire[7:0]   alu_operand_a, alu_operand_b, alu_result;
  wire        alu_zero_out, alu_carry_out;
   
  //Register File Ports
  reg         register_x_write_enable=1'b0;
  wire[7:0]   register_x_data_in;
  wire[7:0]   register_x_data_out, register_y_data_out;
  
  //Scratchpad RAM Ports
  reg         scratch_write_enable=1'b0;
  wire[7:0]   scratch_address;
  wire[7:0]   scratch_data_in, scratch_data_out;
  
  //Instruction Stack Ports
  wire        stack_write_enable, stack_update_enable;
  wire        stack_push_pop;
  wire[9:0]   stack_data_in;
  wire[9:0]   stack_data_out;

/*
****************************************************************************************************
*                                  Instruction Decode Unit (IDU)
****************************************************************************************************
*/      
  psm_idu psm_idu (
    .instruction      (instruction),      
    .operation        (idu_operation),        
    .shift_operation  (idu_shift_operation),   
    .shift_direction  (idu_shift_direction),  
    .shift_constant   (idu_shift_constant), 
    .operand_selection(idu_operand_selection),
    .x_address        (idu_x_address),        
    .y_address        (idu_y_address),        
    .implied_value    (idu_implied_value),    
    .port_address     (idu_port_address),     
    .scratch_address  (idu_scratch_address),  
    .code_address     (idu_code_address),     
    .conditional      (idu_conditional),      
    .condition_flags  (idu_condition_flags),  
    .interrupt_enable (idu_interrupt_enable)      
  );

/*
****************************************************************************************************
*                                    Arithmatic Logic Unit (ALU)
****************************************************************************************************
*/ 
  assign alu_operand_a = register_x_data_out;
  assign alu_operand_b = idu_operand_selection ? register_y_data_out : idu_implied_value;
  psm_alu psm_alu (
    .operation        (idu_operation),      
    .shift_operation  (idu_shift_operation),
    .shift_direction  (idu_shift_direction),
    .shift_constant   (idu_shift_constant), 
    .operand_a        (alu_operand_a),      
    .operand_b        (alu_operand_b),      
    .carry_in         (carry),       
    .carry_out        (alu_carry_out),      
    .zero_out         (alu_zero_out),       
    .result           (alu_result)          
  );
  
/*
****************************************************************************************************
*                                           Register File
****************************************************************************************************
*/    
  assign register_x_data_in = is_fetch ? scratch_data_out : (is_input ? in_port : alu_result);
  psm_register psm_register (
    .clk              (clk),
    .x_address        (idu_x_address),
    .y_address        (idu_y_address),
    .x_data_in        (register_x_data_in),
    .x_write_enable   (register_x_write_enable),
    .x_data_out       (register_x_data_out),
    .y_data_out       (register_y_data_out)
  );  
  
/*
****************************************************************************************************
*                                         Instruction Stack
****************************************************************************************************
*/
  //T=0, instruction is decoded, and conditional_match, is_call, is_return, is_returni is valid, 
  //thus, stack_push_pop is valid, and the position of instruction stack pointer is decided  
  //T=1, stack_data_out is valid, and program_counter is ready to update, and stack_up_date is ready
  //to update too
  //T=2, program_counter is updated to next address, program_counter is always write to the top of
  //the instruction stack when T=2    
  assign stack_data_in = program_counter;
  
`ifdef PSM_JP_NUM
  assign stack_write_enable = internal_reset || timing_control[1];
`else 
  assign stack_write_enable = internal_reset || timing_control[1];
`endif 
  
  assign stack_update_enable = timing_control[1] && 
                               ((conditional_match && (is_call||is_return)) ||
                                is_returni ||
                                interrupt_latch);                             
  assign stack_push_pop = interrupt_latch ||
                          ~((conditional_match&&is_return) || is_returni);
  psm_stack #(.stack_size(stack_size)) psm_stack (
    .clk              (clk),
    .reset            (reset),
    .write_enable     (stack_write_enable),
    .update_enable    (stack_update_enable),
    .push_pop         (stack_push_pop),
    .data_in          (stack_data_in),
    .data_out         (stack_data_out)
  );
  
/*
****************************************************************************************************
*                                           Scratchpad RAM
****************************************************************************************************
*/  
  assign scratch_address = idu_operand_selection ? register_y_data_out : idu_scratch_address;
  assign scratch_data_in = register_x_data_out;
  psm_scratch #(.scratch_size(scratch_size)) psm_scratch (
    .clk              (clk),
    .write_enable     (scratch_write_enable),
    .address          (scratch_address),
    .data_in          (scratch_data_in),
    .data_out         (scratch_data_out)
  );

/*   
****************************************************************************************************
*                                            Miscellaneous
****************************************************************************************************
*/  
  assign address = program_counter;
  assign out_port= register_x_data_out;
  assign port_id = idu_operand_selection ? register_y_data_out : idu_port_address;
  
  always@(posedge clk) begin
    if(reset)
      reset_latch <= 3'b111;
    else
      reset_latch <= {reset_latch[1:0], 1'b0};
  end
  assign internal_reset = reset_latch[2];
  
  //conditional_match is already valid when T=0, because conditional instructions have no register
  //or scratch ram access, and carry and zero are the result of last instruction
  assign conditional_match =  ( ~idu_conditional || 
                                (idu_condition_flags==`flag_c  &&  carry) ||
                                (idu_condition_flags==`flag_nc && ~carry) ||
                                (idu_condition_flags==`flag_z  &&  zero ) ||
                                (idu_condition_flags==`flag_nz && ~zero )
                              ) ? 1'b1 : 1'b0;
                              
  assign  is_jump   = (idu_operation==`op_jump);
  assign  is_call   = (idu_operation==`op_call);
  assign  is_return = (idu_operation==`op_return);
  assign  is_returni= (idu_operation==`op_returni);
  assign  is_input  = (idu_operation==`op_input);
  assign  is_fetch  = (idu_operation==`op_fetch);
  
  always@(interrupt_latch, conditional_match, is_jump, is_call, is_return, is_returni,
          idu_code_address, stack_data_out, program_counter) begin
    if(interrupt_latch) 
      program_counter_source = `interrupt_vector;     //`interrupt_vector is always valid
    else if(conditional_match && (is_jump||is_call))
      program_counter_source = idu_code_address;      //idu_code_address is valid when T=0
    else if((conditional_match&&is_return) || is_returni)
      program_counter_source = stack_data_out;        //stack_data_out is valid when T=1
    else
      program_counter_source = program_counter;       //current PC     
  end
  
  //Note that return and returni has different behavior, when returni instruction is exexuted, the 
  //value saved on the top of the STACK will be loaded to PC, while return instruction is executed
  //the value saved on the top of the STACK will be incremented by 1 and then loaded to PC
  assign program_counter_next = (interrupt_latch ||
                                (conditional_match && (is_jump||is_call)) ||
                                is_returni) ? program_counter_source : program_counter_source+10'd1;
  
  assign interrupt_assert = interrupt && interrupt_enable;
  
  
  task execute;             //Execute is done when T=1
    input [4:0] operation;
    begin
      case (operation)
        `op_load      :     //immediate is ready when T=0, register value is ready when T=1
          register_x_write_enable <= 1'b1;
        `op_and, 
        `op_or, 
        `op_xor, 
        `op_add, 
        `op_addcy, 
        `op_sub, 
        `op_subcy, 
        `op_rs        : begin   //immediate is ready when T=0, register value is ready when T=1
          register_x_write_enable <= 1'b1;
          zero_carry_write_enable <= 1'b1;
        end
        `op_returni   : begin
          zero <= zero_saved;   //zero_saved is ready when T=0
          carry<= carry_saved;  //carry_saved is ready when T=0
          interrupt_enable <= idu_interrupt_enable; //idu_interrupt_enable is ready when T=0
        end
        `op_interrupt :
          interrupt_enable <= idu_interrupt_enable;
        `op_input     : begin
          read_strobe <= 1'b1;
          register_x_write_enable <= 1'b1;
        end
        `op_output    :
          write_strobe <= 1'b1; 
        `op_compare,
        `op_test      :
          zero_carry_write_enable <= 1'b1;
        `op_fetch     :         //scratch ram value is ready when T=1
          register_x_write_enable <= 1'b1;  
        `op_store     :
          scratch_write_enable <= 1'b1;
      endcase
    end  
  endtask
  
  always@(posedge clk) begin
    read_strobe   <= 1'b0;
    write_strobe  <= 1'b0;
    register_x_write_enable <= 1'b0;
    scratch_write_enable <= 1'b0;
    interrupt_ack <= 1'b0;
    zero_carry_write_enable <= 1'b0;
    if(internal_reset) begin
`ifdef PSM_JP_NUM
    timing_control <= {{(`PSM_JP_NUM-1){1'b0}} , 1'b1 };
`else
		timing_control <= 2'b01;
`endif
		
      program_counter <= `reset_vector;
      zero  <= 1'b0;
      carry <= 1'b0;
      interrupt_enable <= 1'b0;
      interrupt_latch  <= 1'b0;
    end
    else begin
`ifdef PSM_JP_NUM
      timing_control <= {timing_control[`PSM_JP_NUM-2:0], timing_control[`PSM_JP_NUM-1]};
`else
		timing_control <= {timing_control[0], timing_control[1]};
`endif 
      interrupt_latch <= interrupt_assert | interrupt_latch;  //
      if(zero_carry_write_enable) begin
          zero <= alu_zero_out;
          carry<= alu_carry_out;
      end
      if(timing_control[1]) begin         //2nd stage T=1
        program_counter <= program_counter_next;
        if(interrupt_latch) begin         //Process interrupt
          interrupt_enable <= 1'b0;       //Interrupt is automatically disabled after entering ISR
          interrupt_latch <= 1'b0;
          interrupt_ack <= 1'b1;          
          zero_saved <= zero;
          carry_saved <= carry;
        end
        else
          execute(idu_operation);
      end
    end
  end
  
endmodule



// soc 调用
module psm_soc #(
    parameter memfile = "firmware.hex" , 
    parameter memnum  =  1024  , 
	parameter stack_size  = 32 ,  // 1 ~ 32 
	parameter scratch_size = 256 // 1 ~ 256  
) (
    input clk  ,
    input reset ,
    input  wire [7:0]  in_port ,
    output wire [7:0]  out_port ,
    output wire [7:0]  port_id,
    output wire        write_strobe ,
    input  wire interrupt ,
    output wire interrupt_ack
); 

function integer log2(input integer d); for(log2=0;|d;log2=log2+1) d=d>>1 ; endfunction 

(* ram_style = "block" *)
reg [17 : 0] mem [0 : memnum-1] /* synthesis syn_ramstyle = "block_ram" */; 
reg [17:0] instruction /* synthesis syn_preserve=1 */ ;
wire [9:0] address /* synthesis syn_preserve=1 */ ;

initial begin 
    if(|memfile) begin 
        $readmemh(memfile , mem ) ; 
        $display("Preloading %m from %s", memfile);
    end  
end 

always @(posedge clk) begin 
	if(~reset)instruction <= mem[address];
end 

psm  #(
	.stack_size(stack_size) ,
	.scratch_size(scratch_size)
)mcu(
    .clk               ( clk         ),   //系统时钟
    .reset             (  reset      ),   //复位  1 --> 复位
    .address           (  address    ),   //程序取址地址
    .instruction       (  instruction),   //指令输入 
    .in_port           ( in_port     ),   //输入口  
    .port_id           ( port_id     ),   //io口地址
    .out_port          ( out_port    ),   //输出口
    .write_strobe      ( write_strobe),   //输出口写使能  
    .read_strobe       ( ),   // 
    .interrupt         ( interrupt),   //中断触发信号
    .interrupt_ack     ( interrupt_ack)   //中断响应信号
);
endmodule

`endif 
